#!/usr/bin/env bash
set -e

test -e /root/.bashrc && . /root/.bashrc
test -e /root/.nvm/nvm.sh && . /root/.nvm/nvm.sh

include() {
    if [ ! -e "$1" ]; then
        curl --max-time 10 -skS "${CI_RUN:-http://mw-gitx.it}/shell/$1" >"/tmp/$1"
        chmod +x "/tmp/$1"
    fi
    # shellcheck disable=SC1090
    test "${2^^}" == "TRUE" && source "/tmp/$1"
    return 0
}

invoke() {
    arr=("$@")
    if test -e "/tmp/${arr[0]}"; then
        arr[0]="/tmp/${arr[0]}"
    elif test -e "/tmp/${arr[1]}"; then
        arr[1]="/tmp/${arr[1]}"
    fi
    echo "Executing ${arr[*]} ... " 1>&2
    "${arr[@]}"
}

sed -i "s@http://.*archive.ubuntu.com@https://mirrors.tuna.tsinghua.edu.cn@g" /etc/apt/sources.list
sed -i "s@http://.*security.ubuntu.com@https://mirrors.tuna.tsinghua.edu.cn@g" /etc/apt/sources.list

IFS=, read -r -a skipped <<<"${CI_SKIP}"
err=0
for rep in "${skipped[@]}"; do
    if [ "${rep}" == "${CI_PROJECT_PATH}" ]; then
        err=1
        break
    fi
done
[ ${err} -eq 1 ] && exit 0

include ci-before.sh
include ci-after.sh
include ci-feishu.sh
include ci-vars.sh true
include ci-prepare.sh

invoke bash ci-prepare.sh

include ci-env.sh true

# shellcheck disable=SC2034
declare -A argv
var=argv parse "$@"

if [ -e "${argv[c]}" ]; then
    declare -A options
    # shellcheck disable=SC1090
    . "${argv[c]}"
fi

invoke bash ci-before.sh "${argv[h]}"
declare -A runtime

function after() {
    if test $? -ne 0; then
        PRIVATE_TOKEN="${options[privatetoken]}" GITLAB_SERVER="${argv[g]}" CI_JOB_STATUS=failed \
            triggered="${runtime[triggered]}" redeployed="${runtime[redeployed]:-}" \
            images="${runtime[images]}" errors="${runtime[errors]}" \
            invoke bash ci-after.sh "${argv[h]}"
        echo "Exiting in 180 seconds ... "
        sleep 180
    else
        # never cache snapshot
        test -e .m2 && find .m2 -regex ".*-SNAPSHOT" -exec rm -rvf {} +
        PRIVATE_TOKEN="${options[privatetoken]}" GITLAB_SERVER="${argv[g]}" CI_JOB_STATUS=success \
            triggered="${runtime[triggered]}" redeployed="${runtime[redeployed]:-}" \
            images="${runtime[images]}" errors="${runtime[errors]}" \
            invoke bash ci-after.sh "${argv[h]}"
    fi
}

trap 'after' SIGHUP SIGINT SIGQUIT SIGTERM EXIT

image_id() {
    docker inspect "$1" 2>/dev/null | grep Id | sed -E 's#^.*sha256:([^"]+).*$#\1#g' | tr -d '\r\n'
}

if ! should_continue; then
    runtime[errors]+=";$(emoji "ghost") skipped"
    exit 0
fi

test -z "${VERBOSE:-}${verbose:-}" || set -x

mapping="null"
group="${CI_PROJECT_NAMESPACE}"
while test -n "${group}"; do
    test "null" != "${mapping}" || mapping="$(echo "${CI_GROUP_MAPPINGS}" | jq -r ".\"${group}\"" 2>/dev/null)"
    test "${group}" != "${group%/*}" || break
    group="${group%/*}"
done
if test "null" = "${mapping}" || test -z "${mapping}"; then
    argv[p]="${CI_GROUP_DEFAULT}"
    argv[r]="${argv[r]%/}/"
    echo "Image group not changed: [${argv[r]}]/[${argv[p]}]"
else
    argv[p]="${mapping}"
    argv[r]="${argv[r]%/}/"
    echo "New image group: [${argv[r]}]/[${argv[p]}]"
fi

cat <<EOF >sha1sum
${CI_COMMIT_TITLE}
${CI_COMMIT_AUTHOR}
${CI_COMMIT_BEFORE_SHA}
${CI_COMMIT_SHA}
${CI_COMMIT_SHORT_SHA}
${CI_COMMIT_TAG}
${CI_COMMIT_DESCRIPTION}
${CI_COMMIT_MESSAGE}
EOF

export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=${JAVA_HOME}/bin:${PATH}
ln -f -s "${JAVA_HOME}/bin/java" /usr/bin/java

node -v
npm -v
mvn -v
java -version
python -V

include ci-helper.js

echo "Fetching files for ${CI_PROJECT_PATH} according ${CI_FETCHING}"
bash -xs <<<"$(invoke node ci-helper.js fetching)"

echo "Preparing for ${CI_PROJECT_PATH} accroding ${CI_PREPARATION} "
bash -xs <<<"$(invoke node ci-helper.js preparation)"

triggered=""
# 1. build
# 1.1 build docker image
# 1.2 upgrade
if [ -e ci.sh ]; then
    bash ci.sh
else
    include upgrade.sh
    declare -A inf
    inf[dependencies]="${PWD}/ci-dependencies.txt"
    prepares=()

    build_options=()
    if [ -n "${argv[o]}" ]; then
        IFS=' ' read -r -a build_options <<<"$(curl --max-time 10 -skS "${argv[o]}?project=${CI_PROJECT_ID}" || echo "")"
    fi

    IFS=' ' read -r -a buildopts <<< "${buildopts:-}"
    for opt in "${buildopts[@]}"
    do
        build_options+=("${opt}")
    done

    IFS=' ' read -r -a extra_options <<<"$(invoke node ci-helper.js options)"

    if [ -e pom.xml ]; then
        evaluate=org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate
        # shellcheck disable=SC2016
        IFS='' read -r -a coordinates <<<"$(echo '${project.groupId}:${project.artifactId}:${project.packaging}:${project.version}:compile' | mvn ${evaluate} -q -DforceStdout)"

        git_commit_info="io.github.git-commit-id:git-commit-id-maven-plugin:5.0.1:revision"
        pmd_verify=""
        grep -q "maven-pmd-plugin" pom.xml || pmd_verify="org.apache.maven.plugins:maven-pmd-plugin:3.19.0:pmd -Dpmd.rulesets=http://mw-gitx.it/p3c/p3c-pmd/src/main/resources/rulesets/java/x-ali.xml"
        echo "Using pmd_verify : ${pmd_verify}"

        # shellcheck disable=SC2016
        read -r jdk <<<"$(echo '${maven.compiler.target}' | mvn ${evaluate} -q -DforceStdout "${extra_options[@]}")"
        if [[ "${jdk}" =~ ^null.* ]]; then
            jdk="$(grep -E '<release>' pom.xml | sed -E 's#\s*<release>([^<]+)</release>#\1#g')" ||
                jdk="$(grep -E '<target>' pom.xml | sed -E 's#\s*<target>([^<]+)</target>#\1#g')"
        fi
        # [root@5cc83dc246f9 jenkins]# alternatives --display java | grep family
        # /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.el7_9.x86_64/jre/bin/java - family java-1.8.0-openjdk.x86_64 priority 1800312
        # /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.el7_9.x86_64/bin/java - family java-11-openjdk.x86_64 priority 1
        # /usr/lib/jvm/java-11-openjdk-11.0.13.0.8-1.el7_9.i386/bin/java - family java-11-openjdk.i386 priority 1
        # /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.312.b07-1.el7_9.i386/jre/bin/java - family java-1.8.0-openjdk.i386 priority 1800312
        case $jdk in
        11)
            # shellcheck disable=SC2155
            export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
            export PATH=${JAVA_HOME}/bin:${PATH}
            ln -f -s "${JAVA_HOME}/bin/java" /usr/bin/java
            ;;
        *)
            git_commit_info=""
            ;;
        esac

        IFS=':' read -r -a parts <<<"${coordinates[0]}"
        inf[project]=${parts[1]}
        inf[version]=${parts[3]}
        inf[image]=${argv[r]%/}/${argv[p]}/${inf[project]}
        if is_for_test; then
            snapshot=$(mvn dependency:resolve -DoutputAbsoluteArtifactFilename | grep -Eo "\-SNAPSHOT:.*" | sed -E 's/^\-SNAPSHOT\:[^:]+\://g' |
                xargs sha1sum | tr '\r\n' ' ' | sha1sum | cut -c 1-8)
        fi
        inf[tag]=$(generate_tag "${inf[version]}" "${snapshot}")

        action=""
        if [ ${#build_options[@]} -le 0 ]; then
            [[ "${build_options[*]}" =~ .*-Dmaven.source.skip=true.* ]] || build_options+=(-Dmaven.source.skip=true)
            [[ "${build_options[*]}" =~ .*-DskipSource=true.* ]] || build_options+=(-DskipSource=true)
            action="org.apache.maven.plugins:maven-deploy-plugin:2.8.2:deploy"
            # shellcheck disable=SC2016
            ver=$(echo '${project.version}' | mvn ${evaluate} -q -DforceStdout)
            if ! [[ "${ver}" =~ .*-SNAPSHOT ]]; then
                if curl --max-time 10 -u "${options[credential]}" -skS "${argv[s]}&group=${parts[0]}&name=${parts[1]}&version=${parts[3]}&maven.extension=${parts[2]}&maven.classifier" | grep -q 'downloadUrl'; then
                    action=""
                fi
            fi
        fi
        [[ "${build_options[*]}" =~ .*-Ddocker.tag=.* ]] || build_options+=(-Ddocker.tag="${inf[tag]}")
        build_options+=(-U)

        echo ",${CI_PMD_SKIP}," | grep -Eq ",${CI_PROJECT_PATH}," && build_options+=("-Dpmd.skip=true")

        err=0
        gav=()
        image=
        while read -r line; do
            echo "${line}"
            if [[ "${line}" =~ ^\[ERROR\].* ]]; then
                err=-1
            fi
            [ $err -eq 0 ] || continue
            if echo -e "${line}" | grep -Eq '^\[INFO\] -*< [^[:space:]]* >-*'; then
                IFS=' ' read -r -a arr <<<"${line}"
                gav+=("${arr[2]}")
            fi
            if echo -e "${line}" | grep -Eq '^\[INFO\] Building [^[:space:]]* [^[:space:]]* *\[[[:digit:]/]*\]'; then
                IFS=' ' read -r -a arr <<<"${line}"
                test ${#gav[@]} -eq 1 && gav+=(${arr[-2]})
            fi
            if echo -e "${line}" | grep -Eq '^\[INFO\] -*\[ [^[:space:]]* \]-*'; then
                IFS=' ' read -r -a arr <<<"${line}"
                test ${#gav[@]} -eq 2 && gav+=(${arr[2]})
            fi
            if [ "${#gav[@]}" -eq 3 ]; then
                coordinate="${gav[0]}:${gav[2]}:${gav[1]}:compile"
                echo "Found coordinate : ${coordinate}."
                coordinates+=("${coordinate}")
                gav=()
            fi

            if echo -e "${line}" | grep -Eq '\[INFO\] Successfully built '; then
                IFS=' ' read -r -a arr <<<"${line}"
                image=${arr[3]}
            fi
            if echo -e "${line}" | grep -Eq '\[INFO\] [^[:space:]]*: digest: sha256:[^[:space:]]+ size: [[:digit:]]+'; then
                inf[id]=$(echo -e "${line}" | grep -Eo 'sha256:[^[:space:]]+' | awk -F: '{print $2}')
                if test -n "${image}"; then
                    if test "${runtime[redeployed]/${image}/}" == "${runtime[redeployed]}"; then
                        runtime[images]+=";$(emoji "whale") ${image}"
                        IFS=':' read -r -a arr <<<"${image}"
                        echo "Redeploying ${arr[0]} ${arr[1]}"
                        releases=$(GIT_PRIVATE_TOKEN=${options[privatetoken]} GIT_API=${argv[g]} invoke bash upgrade.sh "${arr[0]}" "${arr[1]}" "${inf[id]}" "${argv[d]}" | xargs)
                        status=400
                        if [ -n "${releases}" ]; then
                            status=200
                        fi
                        runtime[redeployed]+=";$(http_status_to_emoji "${status}") ${image} => ${releases// /,}"
                    fi
                    image=
                fi
            fi
        done < <(mvn clean \
            ${git_commit_info} \
            org.apache.maven.plugins:maven-dependency-plugin:2.8:list \
            install ${action} \
            ${pmd_verify} \
            ${X_GOAL} \
            "${build_options[@]}" \
            "${extra_options[@]}" \
            -U \
            -Ddocker.registry="${argv[r]%/}/" -Ddocker.project="${argv[p]}" \
            -Dmaven.gitcommitid.nativegit=true -Dmaven.gitcommitid.generateGitPropertiesFile=true \
            -DoutputFile="${inf[dependencies]}" -DappendOutput=true)
        [ ${err} -eq 0 ] || exit 1

        if [ -n "${argv[u]}" ] && [ -e "${inf[dependencies]}" ]; then
            curl --max-time 10 -vskS "${argv[u]}" -F "ref=${CI_BUILD_REF_NAME}" -F "project=${CI_PROJECT_ID}" -F "file=@${inf[dependencies]}" || test 0
        fi

        # add default build task if Dockerfile* exist
        prepares+=("#")
    elif [ -e package.json ]; then
        inf[project]=$(grep -Eo '"name":\s*"[^"]+"' package.json | sed -E 's#^.*"([^"]+)"$#\1#g' | tr -d '\r\n')
        inf[version]=$(grep -Eo '"version":\s*"[^"]+"' package.json | sed -E 's#^.*"([^"]+)"$#\1#g' | tr -d '\r\n')
        inf[image]=${argv[r]%/}/${argv[p]}/${inf[project]}
        inf[tag]=$(generate_tag "${inf[version]}")
        inf[id]=$(image_id "${inf[image]}:${inf[tag]}")
        coordinates=("${inf[project]}")

        npm config -g set registry https://registry.npmmirror.com
        yarn cache clean
        yarn config set registry https://registry.npmmirror.com -g
        yarn config set disturl https://npmmirror.com/mirrors/node -g
        yarn config set electron_mirror https://npmmirror.com/mirrors/electron/ -g
        yarn config set chromedriver_cdnurl https://npmmirror.com/mirrors/chromedriver/ -g
        yarn config set sass_binary_site https://npm.taobao.org/mirrors/node-sass/ -g
        yarn config set phantomjs_cdnurl https://npm.taobao.org/mirrors/phantomjs/ -g
        yarn config set operadriver_cdnurl https://cdn.npm.taobao.org/dist/operadriver -g
        yarn config set fse_binary_host_mirror https://npm.taobao.org/mirrors/fsevents -g
        yarn install --registry https://registry.npmmirror.com || exit 1
        include "ci-npm-builds.js"
        for p in $(invoke node ci-npm-builds.js); do
            if test "${p}" == "build"; then
                prepares+=("#rm -rf dist ; yarn run ${p} ${build_options[@]}")
            else
                prepares+=("_${p:6}#rm -rf dist ; yarn run ${p} ${build_options[@]}")
            fi
        done
        # give a change for Dockerfiles*
        test 0 -lt ${#prepares[@]} || prepares+=("#")
    else
        inf[project]=${CI_PROJECT_NAME,,}
        inf[image]=${argv[r]%/}/${argv[p]}/${inf[project]}
        test -e version && inf[version]=$(cat version)
        inf[tag]=$(generate_tag "${inf[version]}")
        inf[id]=$(image_id "${inf[image]}:${inf[tag]}")
        coordinates=("${inf[project]}")
        # default preparation
        prepares+=("#")
        runtime[errors]+=";$(emoji "warning") Unknown project type"
    fi

    if [ -n "${argv[l]}" ] && [ 0 -lt ${#coordinates[@]} ] && [ -n "${argv[g]}" ]; then
        for coordinate in "${coordinates[@]}"; do
            if [[ "${coordinate}" =~ ^.*-api$ ]]; then
                echo -e "\e[031mSkip api dependencies: ${coordinate}\e[0m"
                continue
            fi
            projects="$(curl --max-time 10 -skS "${argv[l]}?gav=${coordinate}" | tr -d '[]"' || echo "")"
            for p in ${projects//,/ }; do
                project_id="${p%%:*}"
                ref="${p##*:}"
                # do not trigger repeatedly
                test "${triggered//${p}-${ref}/}" == "${triggered}" || continue
                triggered+=":${p}-${ref}"
                test "${CI_PROJECT_ID}" == "${project_id}" && continue
                echo "Triggering the downstream job: ${p} ... "
                token=$(curl --max-time 10 -H "PRIVATE-TOKEN:${options[privatetoken]}" -skS "${argv[g]}/projects/${project_id}/triggers" | grep -Eo '"token":\s*"[^"]+"' | head -n 1 | awk -F\" '{print $4}')
                test -n "${token}" || token=$(curl --max-time 10 -X POST -H "PRIVATE-TOKEN:${options[privatetoken]}" -skS \
                    "${argv[g]}/projects/${project_id}/triggers" -F "description=created-by-auto-devops" |
                    grep -Eo '"token":\s*"[^"]+"' | head -n 1 | awk -F\" '{print $4}')
                if test -n "${token}"; then
                    runtime[triggered]+=" $(curl --max-time 10 -X POST -F "token=${token}" -F "ref=${ref}" -skS "${argv[g]}/projects/${project_id}/trigger/pipeline" |
                        grep -Eo '"web_url":"[^"]+"' | head -n 1 | awk -F\" '{print $4}')"
                fi
            done
        done
    fi

    for ((i = 0; i < ${#prepares[@]}; ++i)); do
        IFS='#' read -r -a id_and_task <<<"${prepares[$i]}"

        if test -n "${id_and_task[1]}"; then
            bash -o xtrace -s <<<"${id_and_task[1]}" || exit 1
        fi

        for dockerfile in *.Dockerfile Dockerfile*; do
            test -e "${dockerfile}" || continue
            test "${dockerfile##*.}" != "dockerignore" || continue
            test -e dist && cp -vf git.properties dist/
            test -e out && cp -vf git.properties out/
            test -z "${X_INCLUDE}" || test "${X_INCLUDE//${dockerfile}/}" != "${X_INCLUDE}" || test "${X_INCLUDE//${dockerfile%.Dockerfile}/}" != "${X_INCLUDE}" || continue
            test -z "${includes}" || test "${includes//${dockerfile}/}" != "${includes}" || test "${includes//${dockerfile%.Dockerfile}/}" != "${includes}" || continue
            if [ "${dockerfile}" != "Dockerfile" ]; then
                if [ "${dockerfile##*.}" == "Dockerfile" ]; then
                    # jdk11.Dockerfile => xxx-jdk11:latest
                    repository="${inf[image]%/*}/${dockerfile:0:-11}${id_and_task[0]}"
                    image="${repository}:${inf[tag]}"
                    project=${inf[project]}-${dockerfile:0:-11}
                else
                    # Dockerfile.jdk11 => xxx-jdk11:latest
                    repository="${inf[image]%/*}/${dockerfile:11}${id_and_task[0]}"
                    image="${repository}:${inf[tag]}"
                    project=${inf[project]}-${dockerfile:11}
                fi
            else
                repository="${inf[image]}${id_and_task[0]}"
                image="${repository}:${inf[tag]}"
                project=${inf[project]}
            fi
            runtime[images]+=";$(emoji "whale") ${image}"
            docker build --no-cache -t "${image}" -f "${dockerfile}" .
            
            inf[id]=$(docker push "${image}" 2>/dev/null | tee /dev/stderr | awk '{print $3}'|cut -c 8-|tr -d '\r\n')
            echo "Got id of ${image}: ${inf[id]}."
            if test "${runtime[redeployed]/${repository}/}" == "${runtime[redeployed]}"; then
                status=400
                releases=$(GIT_PRIVATE_TOKEN=${options[privatetoken]} GIT_API=${argv[g]} invoke bash upgrade.sh "${repository}" "${inf[tag]}" "${inf[id]}" "${argv[d]}" | xargs)
                if [ -n "${releases}" ]; then
                    status=200
                fi
                runtime[redeployed]+=";$(http_status_to_emoji "${status}") ${repository}:${inf[tag]} => ${releases// /,}"
            fi
            status="$(http_status_to_emoji "${status}")" extra="${repository}:${inf[tag]} => pushed" invoke ci-feishu.sh
        done
    done
fi

triggers="$(invoke node ci-helper.js triggers)"
echo "Found pipelines ${triggers:--} of ${CI_PROJECT_PATH}#${CI_COMMIT_REF_NAME} in ${CI_PIPELINES}"
npr=()
while IFS=' ' read -r -a npr; do
    test ${#npr[@]} -ge 2 || continue
    p=${npr[0]}
    ref=${npr[1]}

    test "${triggered//${p}-${ref}/}" == "${triggered}" || continue
    triggered+=":${p}-${ref}"

    echo "Triggering the downstream job: ${p} ... "
    token=$(curl --max-time 10 -H "PRIVATE-TOKEN:${options[privatetoken]}" -skS "${argv[g]}/projects/${p//\//%2f}/triggers" | grep -Eo '"token":\s*"[^"]+"' | head -n 1 | awk -F\" '{print $4}')
    test -n "${token}" || token=$(curl --max-time 10 -X POST -H "PRIVATE-TOKEN:${options[privatetoken]}" -skS \
        "${argv[g]}/projects/${p//\//%2f}/triggers" -F "description=created-by-auto-devops" |
        grep -Eo '"token":\s*"[^"]+"' | head -n 1 | awk -F\" '{print $4}')
    if test -n "${token}"; then
        runtime[triggered]+=" $(curl --max-time 10 -X POST -F "token=${token}" -F "ref=${ref}" -skS "${argv[g]}/projects/${p//\//%2f}/trigger/pipeline" |
            grep -Eo '"web_url":"[^"]+"' | head -n 1 | awk -F\" '{print $4}')"
    fi
done <<<"${triggers}"
